package io.mature.exploit.atom;

import io.mature.extension.migration.MigrateStep;
import io.mature.extension.migration.restore.MetaLimit;
import io.mature.extension.refine.Ox;
import io.mature.stellar.Ok;
import io.mature.stellar.owner.OkA;
import io.r2mo.function.Actuator;
import io.r2mo.function.Fn;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.zerows.epoch.constant.VPath;
import io.zerows.epoch.constant.VString;
import io.zerows.epoch.enums.Environment;
import io.zerows.epoch.common.shared.program.KTimer;
import io.zerows.core.util.Ut;
import io.zerows.extension.mbse.basement.atom.Model;
import io.zerows.extension.mbse.basement.atom.Schema;
import io.zerows.extension.mbse.basement.uca.file.AoFile;
import io.zerows.extension.mbse.basement.uca.file.ExcelReader;
import io.zerows.extension.runtime.ambient.agent.service.application.InitStub;
import io.zerows.specification.access.app.HApp;
import io.zerows.specification.access.app.HArk;
import io.zerows.unity.Ux;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

import static io.mature.extension.refine.Ox.LOG;


/**
 * @author <a href="http://www.origin-x.cn">Lang</a>
 */
public class QModeller {
    private static OkA REFERENCE;
    private final transient String input;
    private final transient String output;
    private Environment environment;
    private Vertx vertxRef;

    private QModeller(final String input, final String output) {
        this.input = this.pathResolve(input);
        this.output = this.pathResolve(output);
    }

    public static QModeller of(final String input, final String output) {
        return new QModeller(input, output);
    }

    private String pathResolve(final String literal) {
        if (Objects.nonNull(literal)) {
            if (literal.endsWith(VString.SLASH)) {
                return literal;
            } else {
                return literal + VString.SLASH;
            }
        } else {
            return null;
        }
    }

    public QModeller bind(final Environment environment) {
        this.environment = environment;
        return this;
    }

    public QModeller bind(final Vertx vertxRef) {
        this.vertxRef = vertxRef;
        return this;
    }

    public void preprocess() {
        this.preprocess(null);
    }

    public void preprocess(final Actuator actuator) {
        this.okA().onComplete(handler -> {
            if (handler.failed()) {
                handler.cause().printStackTrace();
                return;
            }
            final OkA partyA = handler.result();
            final HArk ark = partyA.configArk();
            final HApp app = ark.app();
            /*
             * Timer started
             */
            final KTimer timer = KTimer.of().start();
            final AoFile reader = Ut.singleton(ExcelReader.class, this.input);
            final Set<Model> models = reader.readModels(app.name());
            final Set<Schema> schemata = new HashSet<>();
            /*
             * 1. Model loading
             * 2. Schema loading
             */
            models.forEach(model -> {
                final JsonObject modelJson = model.toJson();
                final String resolved = this.output + "model/" + model.identifier() + VString.DOT + VPath.SUFFIX.JSON;
                LOG.Hub.info(this.getClass(), "Writing Model: {0} -> {1}", model.identifier(), resolved);
                /*
                 * Flush data to output path
                 */
                Ut.ioOut(resolved, modelJson);
                schemata.addAll(model.schema());
            });
            schemata.forEach(schema -> {
                final JsonObject schemaJson = schema.toJson();
                final String resolved = this.output + "schema/" + schema.identifier() + VString.DOT + VPath.SUFFIX.JSON;
                LOG.Hub.info(this.getClass(), "Writing Entity: {0} -> {1}", schema.identifier(), resolved);
                Ut.ioOut(resolved, schemaJson);
            });
            /*
             * Timer end
             */
            timer.end();
            LOG.Hub.info(this.getClass(), "Successfully generation: {0}", timer.value());
            if (Objects.isNull(actuator)) {
                System.exit(0);
            }
            Fn.jvmAt(actuator);
        }).onFailure(Throwable::printStackTrace);
    }

    public void initialize() {
        this.initialize(null);
    }

    public void initialize(final Actuator actuator) {
        this.okA().onComplete(handler -> {
            if (handler.failed()) {
                handler.cause().printStackTrace();
                return;
            }
            final OkA partyA = handler.result();
            final HArk ark = partyA.configArk();
            final HApp app = ark.app();

            final InitStub stub = Ox.pluginInitializer();
            /*
             * Timer started
             */
            final KTimer timer = KTimer.of().start();
            stub.initModeling(app.name(), this.output).compose(initialized -> {
                // #NEW_LOG
                LOG.Atom.info(this.getClass(), "Modeling Environment has been initialized!");
                final MigrateStep step = new MetaLimit(Environment.Development);
                return step.bind(ark).procAsync(new JsonObject());
            }).onSuccess(result -> {
                /*
                 * Timer end
                 */
                timer.end(System.currentTimeMillis());
                // #NEW_LOG
                LOG.Atom.info(this.getClass(), "Modeling Adjustment has been finished: {0}", timer.value());
                if (Objects.isNull(actuator)) {
                    System.exit(0);
                }
                Fn.jvmAt(actuator);
            }).onFailure(Throwable::printStackTrace);
        });
    }

    private Future<OkA> okA() {
        if (Objects.isNull(REFERENCE)) {
            final Vertx container = Objects.isNull(this.vertxRef) ? Ux.nativeVertx() : this.vertxRef;
            final Environment environment = Objects.isNull(this.environment) ? Environment.Development : this.environment;
            return Ok.of(container, environment).compose(okA -> {
                REFERENCE = okA;
                return Ux.future(REFERENCE);
            });
        } else {
            return Ux.future(REFERENCE);
        }
    }
}
